package com.spzx.common.redis.util;

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;
//不可重入的分布式锁
@Component
@Slf4j
public class RedisLockUtil2 {
    @Resource
    StringRedisTemplate stringRedisTemplate;

    //获取分布式锁方法
    public void lock(String lockKey, String uuid, int timeout) {
        //当获取锁失败时，死循环尝试获取锁
        while (!stringRedisTemplate.opsForValue().setIfAbsent(lockKey,
                uuid, timeout, TimeUnit.SECONDS)) {
            try {
                TimeUnit.MILLISECONDS.sleep(80 + new Random().nextInt(100));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        //走出循环 表示setnx成功  获取到锁，再开启看门狗线程对锁自动续期
        renewLock(lockKey, uuid, timeout);
    }

    //锁的自动续期业务
    private void renewLock(String lockKey, String uuid, int timeout) {
        //使用子线程
        new Thread(() -> {
            try {
                //1、第一次判断是否续期：让子线程休眠 timeout*2/3 秒再执行
                TimeUnit.SECONDS.sleep(timeout * 2 / 3);
                //2、判断是否要续期
                // 到redis中获取lockKey的值 如果 和uuid相等 更新锁的过期时间
                // 更新成功返回1  其它情况返回0表示失败(watchdog线程可以结束了)
                String script = "local uuid = redis.call('get' , KEYS[1]) " +
                        " if uuid == ARGV[1] then return redis.call('expire' , KEYS[1] , ARGV[2]) " +
                        " else return 0 end";
                //如果执行更新过期时间的命令返回成功 休眠后 再次执行
                //如果执行更新过期时间的命令失败，结束循环   watchdog线程正常结束
                while (stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),
                        //class java.lang.Integer cannot be cast to class java.lang.String
                        Arrays.asList(lockKey), uuid, timeout + "")) { //execute接收的参数只能是字符串类型
                    TimeUnit.SECONDS.sleep(timeout * 2 / 3);
                }

                //如果不等，表示当前子线程 要监听的锁业务已经结束 已经删除了 看门狗线程可以结束了

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }).start();

    }

    //释放分布式锁方法
    public void unlock(String lockKey , String uuid) {
        String releaseLockScipt = "local uuid = redis.call('get' , KEYS[1]) " +
                "  if( ARGV[1] == uuid) " +
                " then return redis.call('del' , KEYS[1]) " +
                " else return 0 end";
        Long result = stringRedisTemplate.execute(new DefaultRedisScript<>(releaseLockScipt, Long.class),
                Arrays.asList(lockKey), uuid);
        log.info("释放分布式锁的结果: "+ result);
    }
}
